<template>
  <div class="component" :style="Self.itemStyle">
    <div
      v-for="item in MyForm.datas"
      :key="item.name"
      class="form-box"
      :style="{ gridArea: item.name }"
    >
      <div class="form-items" :class="['textarea', 'richtext'].includes(item.type) && 'flex-start'">
        <span
          v-if="!item.customTitle"
          class="title"
          :class="`${MyForm.disable && 'form-disabled'} ${MyForm.size === 'large' && 'form-large'}`"
          :style="{
            textAlign: MyForm.titleAlign,
            width: `${MyForm.titleWidth}px`,
          }"
        >
          <span v-if="item.request && !MyForm.disable" class="request">*</span>
          <span>{{ item.title }}</span>
        </span>
        <!-- 不可编辑且没有值的状态 -->
        <div v-if="MyForm.disable && item.type !== 'custom' && [undefined, null].includes(MyForm.formDatas[item.name])" />
        <!-- 可编辑状态 -->
        <div
          v-else
          class="item"
          :class="MyForm.disable && 'form-disabled'"
          :style="{ paddingRight: MyForm.fixRight }"
        >
          <!-- 文本显示，可用于禁用状态 -->
          <template v-if="item.type === 'text'">
            <div v-if="MyForm.disable" class="pad-text">
              {{ MyForm.formDatas[item.name] }}
            </div>
            <a-input v-else :value="MyForm.formDatas[item.name]" disabled />
          </template>
          <!-- 输入框，适用于一般输入 -->
          <template v-if="item.type === 'input'">
            <a-input
              :value="MyForm.formDatas[item.name]"
              allow-clear
              :type="item.subType || 'text'"
              :placeholder="item.hint || `请输入`"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            >
              <template #prefix>
                <slot :name="item.icon" />
              </template>
            </a-input>
          </template>
          <!-- 文本域，适用于较长的文本输入 -->
          <template v-if="item.type === 'textarea'">
            <div v-if="MyForm.disable" class="pad-text">
              {{ MyForm.formDatas[item.name] }}
            </div>
            <a-textarea
              v-else
              :value="MyForm.formDatas[item.name]"
              allow-clear
              :placeholder="item.hint || `请输入`"
              :auto-size="{ minRows: item.min, maxRows: item.max }"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 选择器，适用于多个选项 -->
          <template v-if="item.type === 'select'">
            <a-select
              :value="MyForm.formDatas[item.name]"
              allow-clear
              :mode="item.mode"
              :placeholder="item.hint || `请选择`"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            >
              <a-select-option
                v-for="selection in item.items"
                :key="selection.id"
                :value="selection.id"
              >
                {{ selection.name }}
              </a-select-option>
            </a-select>
          </template>
          <!-- 级联选择器，适用于多级选择 -->
          <template v-if="item.type === 'cascader'">
            <a-cascader
              :value="MyForm.formDatas[item.name]"
              allow-clear
              :options="item.items"
              :field-names="item.field"
              :load-data="item.load"
              :placeholder="item.hint || `请选择`"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 开关选择 -->
          <template v-if="item.type === 'switch'">
            <a-switch
              :checked="MyForm.formDatas[item.name]"
              :disabled="MyForm.disable"
              class="switch"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 日期选择器，适用于只选择一个日期 -->
          <template v-if="item.type === 'date'">
            <a-date-picker
              :value="MyForm.formDatas[item.name]"
              allow-clear
              value-format="YYYY-MM-DD"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 时间日期选择器 -->
          <template v-if="item.type === 'datetime'">
            <a-date-picker
              :value="MyForm.formDatas[item.name]"
              allow-clear
              value-format="YYYY-MM-DD HH:mm:ss"
              :size="MyForm.size"
              :show-time="{ defaultValue: dayjs('00:00:00', 'HH:mm:ss') }"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 日期范围选择器，适用于选择起始日期和结束日期 -->
          <template v-if="item.type === 'range'">
            <a-range-picker
              :value="MyForm.formDatas[item.name]"
              allow-clear
              value-format="YYYY-MM-DD"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 时间范围选择器，适用于选择起始时间和结束时间 -->
          <template v-if="item.type === 'timerange'">
            <a-time-range-picker
              :value="MyForm.formDatas[item.name]"
              allow-clear
              value-format="HH:mm:ss"
              :size="MyForm.size"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 时间日期范围选择器，适用于选择起始日期时间和结束日期时间 -->
          <template v-if="item.type === 'datetimerange'">
            <a-range-picker
              :value="MyForm.formDatas[item.name]"
              allow-clear
              value-format="YYYY-MM-DD HH:mm:ss"
              :size="MyForm.size"
              :show-time="{ defaultValue: dayjs('00:00:00', 'HH:mm:ss') }"
              @change="(val) => Self.onFormChange(item.name, val)"
            />
          </template>
          <!-- 文件选择器 -->
          <template v-if="item.type === 'file'">
            <a-button
              type="primary"
              style="max-width: 125px"
              @click="Self.onSelectFile(item)"
            >
              点击选择文件
            </a-button>
          </template>
          <!-- 富文本编辑器 -->
          <template v-if="item.type === 'richtext'">
            <div :id="`rich-${item.name}`" />
          </template>
          <!-- 图片 -->
          <template v-if="item.type === 'image'">
            <div class="image">
              <template v-if="MyForm.formDatas[item.name]">
                <a-image
                  width="100%"
                  :height="item.height || '300px'"
                  :src="MyForm.formDatas[item.name].path"
                />
                <close-circle-outlined
                  v-if="!MyForm.disable"
                  class="close-icon"
                  @click="Self.onClear(item)"
                />
              </template>
              <template v-else>
                <div
                  v-if="!MyForm.disable"
                  class="placeholder"
                  @click="Self.onSelectFile(item)"
                >
                  <plus-outlined class="icon" />
                  <div class="title">
                    上传图片
                  </div>
                </div>
                <a-empty v-else description="暂无图片" />
              </template>
            </div>
          </template>
          <!-- 自定义 -->
          <template v-if="item.type === 'custom'">
            <slot :name="item.name" />
          </template>
        </div>
      </div>
      <!-- 表单底部附加自定义 -->
      <div :style="{ marginLeft: `${MyForm.titleWidth + 10}px` }">
        <div class="request-hint">
          <div v-if="item._isEmpty && !MyForm.disable">
            请填写{{ item.title }}
          </div>
        </div>
        <slot :name="item.append" />
      </div>
    </div>
    <!-- 表单按钮 -->
    <a-space
      v-if="!MyForm.hideBtn"
      size="middle"
      class="form-btns"
      :style="Self.btnStyle"
    >
      <template v-for="item in MyForm.btns" :key="item.name">
        <a-dropdown v-if="item.items">
          <template #overlay>
            <a-menu @click="item.callback">
              <a-menu-item v-for="selection in item.items" :key="selection.id">
                {{ selection.name }}
              </a-menu-item>
            </a-menu>
          </template>
          <a-button
            :type="item.type"
            :size="MyForm.size"
            :disabled="item.disable"
          >
            <span>{{ item.name }}</span>
            <down-outlined />
          </a-button>
        </a-dropdown>
        <a-button
          v-else
          :type="item.type"
          :size="MyForm.size"
          :disabled="item.disable"
          @click="item.callback"
        >
          <slot :name="item.icon" />
          <span>{{ item.name }}</span>
        </a-button>
      </template>
    </a-space>
  </div>
</template>

<script setup>
/* eslint-disable max-lines */
import { reactive } from "@vue/reactivity";
import { inject, onMounted } from "@vue/runtime-core";
import dayjs from "dayjs";
import { uploadFile } from "@/configs/controller";
import { host } from "@/configs/run";

import { DownOutlined, PlusOutlined, CloseCircleOutlined } from "@ant-design/icons-vue";
import RichTextEditor from "wangeditor";

/**
 * -- input --
 * cols {number} 网格列数
 * gap {number[]} 长度为 2 的数组，分别是水平间距和垂直间距
 * datas {Array} 表单配置
 * btns {Array} 按钮配置
 * titleAlign {string} 表单标题对齐方式，取值为 CSS 属性值
 * btnAlign {string} 按钮对齐方式，取值范围为 left | right | center
 * btnMargin {number} 按钮边距，取值为 CSS 属性值
 * hideBtn {boolean} 是否显示按钮
 * areas {string} 网格排列方式，按钮已命名为 btn
 * formDatas {object} 表单内容
 * fixRight {number} 表单右侧补充间距
 * initDatas {Function} 表单初始化数据方法
 * changeCallback {Function} 表单值改变的回调方法
 */

/**
 * -- output --
 * onInitStyle {Function} 重置样式
 * checkValidate {Function} 检查表单值
 */

// props
// eslint-disable-next-line no-undef
const props = defineProps({
  refName: {
    type: String,
    default: "MyForm",
  },
  formDatasIndex: {
    type: Number,
    default: -1,
  },
});

// def
const Self = reactive({});
const MyForm = inject(props.refName);
const userInfo = inject("userInfo");

// datas
Self.itemStyle = {};
Self.btnStyle = {};

MyForm.disable ||= false;
MyForm.size ||= "middle";
MyForm.cols ||= 5;
MyForm.gap ||= [10, 10];
MyForm.areas ||= "";
MyForm.datas ||= [];
MyForm.btns ||= [];
MyForm.titleAlign ||= "center";
MyForm.titleWidth ||= 64;
MyForm.btnAlign ||= "left";
MyForm.btnMargin ||= "";
MyForm.hideBtn ||= false;
MyForm.formDatas ||= {};
MyForm.fixRight ||= "0px";
MyForm.initDatas ||= () => null;
MyForm.changeCallback ||= () => null;

// methods
Self.onFormChange = (name, val) => {
  let value = undefined;
  if ([undefined, null].includes(val)) {
    value = undefined;
  } else {
    value = val.target ? val.target.value : val;
  }
  if (Array.isArray(MyForm.formDatas)) {
    MyForm.formDatas[props.formDatasIndex][name] = value;
    MyForm.changeCallback(props.formDatasIndex, name, value);
  } else {
    MyForm.formDatas[name] = value;
    MyForm.changeCallback(name, value);
  }

  // 验证表单
  MyForm.checkValidate(name);
};

Self.onClear = (item) => {
  MyForm.formDatas[item.name] = null;
};

Self.onSelectFile = (item) => {
  const el = document.createElement("input");
  el.setAttribute("type", "file");
  el.setAttribute("accept", item.accept || "*");
  if (item.multi) {
    el.setAttribute("multiple", "multiple");
  }
  el.addEventListener("change", async () => {
    if (!item.multi) {
      MyForm.formDatas[item.name] = el.files[0];
    } else if (item.cover) {
      MyForm.formDatas[item.name] = Array.from(el.files);
    } else {
      MyForm.formDatas[item.name] = [
        ...(MyForm.formDatas[item.name] || []),
        ...Array.from(el.files),
      ];
    }
    if (item.upload) {
      let uploadFiles = MyForm.formDatas[item.name];
      if (!item.multi) {
        uploadFiles = [uploadFiles];
      }
      let uploads = uploadFiles.map(async (v) => {
        if (v instanceof File) {
          const res = await uploadFile(v);
          return {
            name: v.name,
            path: `${host}/${res.data}`,
          };
        } else {
          return v;
        }
      });
      uploads = await Promise.allSettled(uploads);
      if (!item.multi) {
        // eslint-disable-next-line require-atomic-updates
        MyForm.formDatas[item.name] = uploads.map((v) => v.value)[0];
      } else {
        // eslint-disable-next-line require-atomic-updates
        MyForm.formDatas[item.name] = uploads.map((v) => v.value);
      }
    }
  });
  el.click();
};

Self.onInitEditor = () => {
  MyForm.datas.forEach((v) => {
    if (v.type === "richtext") {
      v.ref = new RichTextEditor(document.getElementById(`rich-${v.name}`));
      v.ref.config.height = v.height;
      v.ref.config.zIndex = 2;
      v.ref.config.uploadFileName = "file";
      v.ref.config.uploadImgServer = v.uploadUrl;
      v.ref.config.uploadImgHeaders = {};
      v.ref.create();
    }
  });
};

MyForm.onInitStyle = () => {
  // item style
  Self.itemStyle.width = `calc(100% - ${(MyForm.cols - 1) * MyForm.gap[0]}px)`;
  Self.itemStyle.columnGap = `${MyForm.gap[0]}px`;
  Self.itemStyle.rowGap = `${MyForm.gap[1]}px`;
  Self.itemStyle.gridTemplateColumns = `repeat(${MyForm.cols}, ${
    100 / MyForm.cols
  }%)`;
  Self.itemStyle.gridTemplateAreas = MyForm.areas;

  // btn style
  if (MyForm.btnAlign === "left") {
    Self.btnStyle.justifyContent = "flex-start";
  } else if (MyForm.btnAlign === "center") {
    Self.btnStyle.justifyContent = "center";
  } else if (MyForm.btnAlign === "right") {
    Self.btnStyle.justifyContent = "flex-end";
  }
  Self.btnStyle.margin = MyForm.btnMargin;
};

MyForm.checkValidate = (name) => {
  let isNoEmpty = true;
  let datas = [];
  if (name) {
    datas = MyForm.datas.filter((val) => val.name === name);
  } else {
    datas = MyForm.datas;
  }
  datas.forEach((val) => {
    if (val.request) {
      if ([undefined, null].includes(MyForm.formDatas[val.name])) {
        val._isEmpty = true;
        isNoEmpty = false;
      } else {
        val._isEmpty = false;
      }
    }
  });
  return isNoEmpty;
};

MyForm.clearValidate = (name) => {
  let datas = [];
  if (name) {
    datas = MyForm.datas.filter((val) => val.name === name);
  } else {
    datas = MyForm.datas;
  }
  datas.forEach((val) => {
    val._isEmpty = false;
  });
};

// lifecycle
onMounted(() => {
  MyForm.onInitStyle();
  MyForm.clearValidate();
  Self.onInitEditor();
});
</script>

<style lang="less" scoped>
@import "./index";
</style>
